package se.kth.ict.id2203.assignment4;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import se.kth.ict.id2203.console.Console;
import se.kth.ict.id2203.console.ConsoleLine;
import se.kth.ict.id2203.uniformconsensus.UcDecide;
import se.kth.ict.id2203.uniformconsensus.UcPropose;
import se.kth.ict.id2203.uniformconsensus.UniformConsensus;

import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Kompics;
import se.sics.kompics.Positive;
import se.sics.kompics.Start;
import se.sics.kompics.timer.ScheduleTimeout;
import se.sics.kompics.timer.Timer;

public final class Application4 extends ComponentDefinition {

	Positive<Console> con = requires(Console.class);
	
	Positive<Timer> timer = requires(Timer.class);
	Positive<UniformConsensus> puc = requires(UniformConsensus.class);

	private static final Logger logger =
			LoggerFactory.getLogger(Application4.class);

	private List<String> commands;
	private boolean blocking;
	
	private boolean printAfterBlocking;
	
	private boolean shouldSleepAfterCompleted;
	private long sleepTime;
	
	private TreeMap<Integer,Integer> completedDecisions;
	private TreeMap<Integer,Integer> pendingDecisions;

	/**
	 * Instantiates a new application1a.
	 */
	public Application4() {
		subscribe(handleInit, control);
		subscribe(handleStart, control);
		subscribe(handleContinue, timer);
		subscribe(handleConsoleInput, con);
		
		subscribe(handleUcDecide, puc);
//		subscribe(handleWriteReturn, puc);
	}

	Handler<Application4Init> handleInit = new Handler<Application4Init>() {
		public void handle(Application4Init event) {

			commands = new ArrayList<String>(Arrays.asList(event.getCommandScript().split(":")));
            commands.add("$DONE");
	        blocking = false;
	        printAfterBlocking = false;
	        shouldSleepAfterCompleted = false;
	        sleepTime = 0;
	        
	        completedDecisions = new TreeMap<Integer,Integer>();
	        pendingDecisions = new TreeMap<Integer,Integer>();

		}
	};

	Handler<Start> handleStart = new Handler<Start>() {
		public void handle(Start event) {
			doNextCommand();
		}
	};

	Handler<ApplicationContinue> handleContinue = new Handler<ApplicationContinue>() {
		public void handle(ApplicationContinue event) {
			logger.info("Woke up from sleep");
			blocking = false;
			doNextCommand();
		}
	};
	
	Handler<UcDecide> handleUcDecide = new Handler<UcDecide>() {
	public void handle(UcDecide event) {
		logger.info("");
		logger.info("");
		logger.info("==================>UcDecide event received! [Instance id: "
				+ event.getId() + " Value: " + event.getDecision() + "]");
		
		pendingDecisions.remove( event.getId() );
		completedDecisions.put( event.getId() , event.getDecision());
//		blocking = false;
		
		/*
		 * If all pending decisions are done then stop blocking.
		 */
		if ( pendingDecisions.isEmpty() )
		{
			blocking = false;
			if ( printAfterBlocking )
			{
				print();
				printAfterBlocking = false;
			}
			
			if ( shouldSleepAfterCompleted )
			{
				doSleep(sleepTime);
				shouldSleepAfterCompleted = false;
			}
		}
		
		doNextCommand();
	}
};
	

	Handler<ConsoleLine> handleConsoleInput = new Handler<ConsoleLine>() {
		@Override
		public void handle(ConsoleLine event) {
			commands.addAll(Arrays.asList(event.getLine().trim().split(":")));
			doNextCommand();
		}
	};

	private final void doNextCommand() {
		while (!blocking && !commands.isEmpty()) {
            doCommand(commands.remove(0));
		}
	}

	private void doCommand(String cmd) {
		if (cmd.startsWith("S")) {
			doSleep(Integer.parseInt(cmd.substring(1)));
		} else if (cmd.startsWith("X")) {
			doShutdown();
		} else if (cmd.equals("help")) {
			doHelp();
			} else if (cmd.startsWith("P")) {
				
				String input[] = cmd.substring(1).trim().split("-");
			doPropose(Integer.parseInt(input[0]), Integer.parseInt(input[1]));
			
			} else if (cmd.startsWith("D")) {

			doWaitPending(Long.parseLong(cmd.substring(1)));

			} else if (cmd.startsWith("W")) {
//				doWaitPendingAndPrintThem();
				print();
			
//		} else if (cmd.startsWith("R")) {
//			blocking = true;
//			doRead(Integer.parseInt(cmd.substring(1)));
//			
//		} else if (cmd.startsWith("W")) {
//			blocking = true;
//			String wrst = cmd.substring(1);
//			String wrarg[] = wrst.trim().split(",");
//			doWrite(Integer.parseInt(wrarg[0]), Long.parseLong(wrarg[1]));
//			
		} else if (cmd.equals("$DONE")) {
			logger.info("DONE ALL OPERATIONS");
		} else {
			logger.info("Bad command: '{}'. Try 'help'", cmd);
		}
	}

	private final void doHelp() {
		logger.info("Available commands: P<m>, L<m>, S<n>, help, X");
		logger.info("Pm: sends perfect message 'm' to all neighbors");
		logger.info("Lm: sends lossy message 'm' to all neighbors");
		logger.info("Sn: sleeps 'n' milliseconds before the next command");
		logger.info("help: shows this help message");
		logger.info("X: terminates this process");
	}
	
	private void print()
	{
		logger.info("");
		logger.info("");
		logger.info("      Printing all completed decisions:");
		
		SortedSet<Integer> keys = new TreeSet<Integer>(completedDecisions.keySet());
		for (Integer key : keys) { 
			Integer value = completedDecisions.get(key);
			
			logger.info("          instance:"+key+", value="+value);
		}
	}
	
	private void doWaitPendingAndPrintThem()
	{
		if ( !pendingDecisions.isEmpty() )
		{
			logger.info("Waiting for pending decisions...");
			printAfterBlocking = true;
			blocking = true;
		}
		else
		{
			print();
		}
	}
	
	private void doWaitPending( long delay )
	{
		if ( !pendingDecisions.isEmpty() )
		{
			logger.info("Waiting for pending decisions...");
			blocking = true;
			shouldSleepAfterCompleted = true;
			sleepTime = delay;
		}
	}

	private void doSleep(long delay) {
		logger.info("Sleeping {} milliseconds...", delay);

		ScheduleTimeout st = new ScheduleTimeout(delay);
		st.setTimeoutEvent(new ApplicationContinue(st));
		trigger(st, timer);
		
		blocking = true;
	}

	private void doShutdown() {
		System.out.println("2DIE");
		System.out.close();
		System.err.close();
		Kompics.shutdown();
		blocking = true;
	}
	
	private void doPropose( int id, int value )
	{
		logger.info("");
		logger.info("");
		logger.info("Trigger Propose(" + id + ", " + value + ")");
		pendingDecisions.put(id, value);
		trigger(new UcPropose(id, value), puc);	
	}
	
//	private void doWrite(int register, long val) {
//		logger.info("Trigger WriteResquest(" + register + ", " + val + ")");
//		trigger(new WriteRequest(register, val), riwcm);		
//	}
//	
//	private void doRead(int register) {
//		logger.info("Trigger ReadResquest(" + register + ")");
//		trigger(new ReadRequest(register), riwcm);	
//	}
}
